package me.ingxin.android.router.compiler.processor

import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import me.ingxin.android.router.annotaions.App
import me.ingxin.android.router.annotaions.compiler.LExtInfo
import me.ingxin.android.router.annotaions.compiler.LRoute
import me.ingxin.android.router.annotaions.compiler.LRouteIndex
import me.ingxin.android.router.compiler.util.*
import me.ingxin.android.router.annotaions.route.IRouteWarehouse
import me.ingxin.android.router.annotaions.route.RouteMeta
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.PackageElement

/**
 * Created by ingxin on 2022/4/15
 */
class RouteAppProcessor(processorUtil: ProcessorUtil, logger: Logger) :
    BaseProcessor(processorUtil, logger) {

    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        return HashSet<String>().apply { add(App::class.java.name) }
    }

    fun checkApp(roundEnv: RoundEnvironment): String? =
        processorUtil.checkApp("RouteAppProcessor", roundEnv, logger)

    fun process(appOwner: String, appRouteMap: MutableMap<String, LRoute>?) {
        writeRouteWarehouse(appOwner, appRouteMap)
    }

    /**
     * 合并所有路由
     * @param appOwner 使用@[App]的类
     * @param appRouteMap 当前module所包含的路由
     */
    private fun writeRouteWarehouse(
        appOwner: String,
        appRouteMap: MutableMap<String, LRoute>?
    ) {
        //检测是否重复生成
        val routeWarehouse = processorUtil.env.elementUtils
            .getTypeElement("${ROUTE_WAREHOUSE_PACKAGE_NAME}.${ROUTE_WAREHOUSE_CLASS_NAME}")
        if (routeWarehouse != null) {
            val creator = routeWarehouse.getAnnotation(LExtInfo::class.java)?.creator
            if (appOwner != creator) {
                logger.throwException("Duplicate annotation [@${App::class.java.simpleName}] found in class [$appOwner] and class [$creator]")
            }
        }

        //获取目标package所有类
        val routeIndexPackage =
            processorUtil.env.elementUtils.getPackageElement(ROUTE_INDEX_PACKAGE_NAME)
        val routeMap = if (routeIndexPackage != null) {
            getAllRoute(routeIndexPackage, appRouteMap ?: HashMap())
        } else {
            appRouteMap ?: HashMap()
        }

        //routes成员变量
        val routesProperty = PropertySpec.builder(
            "routes",
            HashMap::class.asClassName()
                .parameterizedBy(String::class.asClassName(), RouteMeta::class.asClassName()),
            KModifier.PRIVATE
        )
            .initializer("%T()", HashMap::class)
            .build()

        //init 代码块,用“·”声明不换行空格
        val ibb = CodeBlock.builder()
        for (route in routeMap.values) {

            ibb.add("routes[\"${route.path}\"]·=·%T(", RouteMeta::class)

            //interceptorName
            if (route.interceptorName.isEmpty()) {
                ibb.add("null,·")
            } else {
                ibb.add("arrayOf(")
                for ((i, e) in route.interceptorName.withIndex()) {
                    ibb.add("\"$e\"")
                    if (i != route.interceptorName.size - 1) {
                        ibb.add(",·")
                    }
                }
                ibb.add(")").add(",·")
            }

            //interceptor
            if (route.interceptor.isEmpty()) {
                ibb.add("null,·")
            } else {
                ibb.add("arrayOf(")
                for ((i, e) in route.interceptor.withIndex()) {
                    ibb.add("%T::class.java", getClassName(e))
                    if (i != route.interceptor.size - 1) {
                        ibb.add(",·")
                    }
                }
                ibb.add(")").add(",·")
            }

            //hold
            ibb.add("${route.hold}").add(",·")

            //destination
            ibb.add("%T::class.java", getClassName(route.destination))
                .add(")")

            ibb.add("\n")
        }


        val funcSpec = FunSpec.builder("getRouteList")
            .addModifiers(KModifier.OVERRIDE)
            .returns(
                HashMap::class.asClassName()
                    .parameterizedBy(
                        String::class.asClassName(),
                        RouteMeta::class.asClassName()
                    )
            )
            .addStatement("return routes")
            .build()

        //增加构建信息
        val annotationSpec = AnnotationSpec.builder(LExtInfo::class)
            .addMember(
                CodeBlock.builder()
                    .add("""creator·=·"$appOwner"""").build()
            )
            .build()

        val classSpec = TypeSpec.classBuilder(ROUTE_WAREHOUSE_CLASS_NAME)
            .addProperty(routesProperty)
            .addAnnotation(annotationSpec)
            .addInitializerBlock(ibb.build())
            .addSuperinterface(IRouteWarehouse::class)
            .addFunction(funcSpec)
            .build()

        processorUtil.writeClass(ROUTE_WAREHOUSE_PACKAGE_NAME, classSpec)
    }

    private fun getClassName(name: String): ClassName {
        val lastIndex = name.lastIndexOf(".")
        val classPackage = name.substring(0, lastIndex)
        val className =
            name.substring(lastIndex + 1, name.length)
        return ClassName(classPackage, className)
    }


    /**
     * 获取包下所有路由
     */
    private fun getAllRoute(
        packageElement: PackageElement,
        routeMap: MutableMap<String, LRoute>
    ): MutableMap<String, LRoute> {
        //遍历包下所有类
        for (element in packageElement.enclosedElements) {
            val annotation = element.getAnnotation(LRouteIndex::class.java) ?: continue
            for (item in annotation.route) {
                val routePath = item.path
                val className = item.destination
                if (routeMap[routePath] != null) {
                    if (routeMap[routePath]?.destination == className) {
                        //路由相同并且类也相同，说明module新增了一个App注解或者有重复类，不需要处理
                        logger.warning("a annotation [@${App::class.java.simpleName}] was added, skip route")
                        continue
                    }
                    logger.throwException("Duplicate route path [$routePath] found in class [${routeMap[routePath]?.destination}] and class [$className]")
                }
                routeMap[routePath] = item
            }
        }
        return routeMap
    }

}